#include <winsock2.h>
#include "MySQL/mysql.h"
#include "CUtil.h"
#include "CChar.h"
#include "CBlowfish.h"
#include "CConsole.h"

#include "CDb.h"


CDb* CDb::_instance = NULL;

//sem_t CDb::numMysqlConnSemaphore;
CSemaphore* CDb::numMysqlConnSemaphore; 
int CDb::numMysqlConn;

//sem_t CDb::mysqlConnListCountSemaphore;
//sem_t CDb::mysqlConnListMutex;
CSemaphore* CDb::mysqlConnListCountSemaphore;
CSemaphore* CDb::mysqlConnListMutex;
std::vector<MYSQL*> CDb::mysqlConnList;

void CDb::loadWeaponskillRanks(void)
{
	CConsole::outDebOnly("Loading weaponskill ranks!");
	MYSQL_RES * res;
	MYSQL_ROW row;
	MYSQL* mysqlConn;
	char query[1024];
	memset(query, 0, 1024);
	int count;

	sprintf(query, "SELECT max(id) FROM skill_ranks");
	count = (int) strlen(query);

	mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if (!res) 
	{
		CConsole::outErr("Query to determine the number of weaponskills failed!");
		return;
	}

	row = mysql_fetch_row(res);
	unsigned char numWeaponSkills = atoi(row[0]);
	mysql_free_result(res);

	if (numWeaponSkills == 0)
	{
		CConsole::outErr("Failed to load weapon skill ranks from db! No weapon skills in table skill_ranks");
		return;
	}

	maxWeaponSkillId = numWeaponSkills;

	m_pSkillRanks = new char*[NUMJOBS]; 
	for (int job = 0; job < NUMJOBS; job++)
	{
		m_pSkillRanks[job] = new char[numWeaponSkills];
		for (int wsId = 0; wsId < numWeaponSkills; wsId++)
			m_pSkillRanks[job][wsId] = 0;
	}

	sprintf(query, "SELECT id,WAR,MNK,WHM,BLM,RDM,THF,PLD,DRK,BST,BRD,RNG,SAM,NIN,DRG,SMN,BLU,COR,PUP,DNC,SCH FROM skill_ranks ORDER BY skill_ranks.id ASC");
	count = (int) strlen(query);

	mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if (!res) 
	{
		CConsole::outErr("Failed to query all weaponskills ranks!");
		return;
	}

	int weaponSkillId;

	while ((row = mysql_fetch_row(res))!=NULL)
	{
		weaponSkillId = atoi(row[0]);
		for (int job = 1; job<=NUMJOBS; job++)
			m_pSkillRanks[job-1][weaponSkillId-1] = atoi(row[job]);
	}

	CConsole::outDebOnly("Successfully loaded weaponskill ranks!");
	mysql_free_result(res);
}

void CDb::loadWeaponskillCaps(void)
{
	CConsole::outDebOnly("Loading weaponskill caps!");
	MYSQL_RES * res;
	MYSQL_ROW row;
	MYSQL* mysqlConn;
	char query[1024];
	memset(query, 0, 1024);
	int count;

	sprintf(query, "SELECT max(level) FROM skill_caps");
	count = (int) strlen(query);

	mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if (!res) 
	{
		CConsole::outErr("Query to determine the number of levels failed!");
		return;
	}

	row = mysql_fetch_row(res);
	int numLevels = atoi(row[0]);
	mysql_free_result(res);

	if (numLevels == 0)
	{
		CConsole::outErr("Failed to load the number of levels from db! No Entries in the skill_caps table found!");
		return;
	}

	m_pSkillCap = new unsigned short*[NUMRANKS]; 
	for (int rank = 0; rank < NUMRANKS; rank++)
	{
		m_pSkillCap[rank] = new unsigned short[numLevels];
		for (int level = 0; level < numLevels; level++)
			m_pSkillCap[rank][level] = 0;
	}

	sprintf(query, "SELECT level,`0`,`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`,`10`,`11` FROM skill_caps ORDER BY skill_caps.level ASC");
	count = (int) strlen(query);

	mysqlConn=CDb::getConn(); //get conn
	mysql_real_query(mysqlConn, query, count);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn); //release conn

	if (!res) 
	{
		CConsole::outErr("Failed to query all weaponskills caps!");
		return;
	}

	int level;

	while ((row = mysql_fetch_row(res))!=NULL)
	{
		level = atoi(row[0]);
		for (int rank = 1; rank<=NUMRANKS; rank++)
			m_pSkillCap[rank-1][level-1] = atoi(row[rank]);
	}
	maxLevel = level;

	CConsole::outDebOnly("Successfully loaded weaponskill caps!");
	mysql_free_result(res);
}

unsigned short CDb::getMaxWeaponSkill(unsigned short weaponSkillId, unsigned char jobId, unsigned char level)
{
	if ((level < 1) || (level > maxLevel))
		return 0;
	if ((weaponSkillId < 1) || (weaponSkillId>=maxWeaponSkillId))
		return 0;
	if (jobId < 1)
		return 0;
	unsigned char rank = m_pSkillRanks[jobId-1][weaponSkillId-1];
	return m_pSkillCap[rank][level-1];
}

CDb::CDb() 
{ 
}

CDb::~CDb() {

}

CDb* CDb::getObj() {
	if(!_instance) {
		_instance = new CDb();
	}
	return _instance;
}

MYSQL* CDb::getConn() 
{
	//Wait for an unused connection to use
	CDb::mysqlConnListCountSemaphore->forceLock();

	//make sure we are the only one accessing the connection list
	CDb::mysqlConnListMutex->forceLock();

	MYSQL* retVal=NULL;

	if (CDb::mysqlConnList.size()==0)
	{
		CConsole::outErr("No MYSQL Connection left, but there should have been at least one.");
	}
	else
	{
		retVal=CDb::mysqlConnList.back();
		mysqlConnList.pop_back();
		if (retVal==NULL)
			CConsole::outErr("Got a MYSQL null struct from the vector.");
	}

	//release semaphore
	CDb::mysqlConnListMutex->forceUnlock();
	
	return retVal;
}

void CDb::pushConn(MYSQL* mysql)
{
	//make sure we are the only one accessing the connection list
	CDb::mysqlConnListMutex->forceLock();

	CDb::mysqlConnList.push_back(mysql);

	//release semaphore, to allow other threads to use this connection
	CDb::mysqlConnListCountSemaphore->forceUnlock();

	//release semaphore
	CDb::mysqlConnListMutex->forceUnlock();
}


bool CDb::startMySQL() {
	char server[100], password[50], user[50], database[50];
	int port;
	int numConn;

	CUtil::getINIstring("configure.ini", "MYSQL", "SERVER", server);
	CUtil::getINIstring("configure.ini", "MYSQL", "USER", user);
	CUtil::getINIstring("configure.ini", "MYSQL", "PASS", password);
	CUtil::getINIstring("configure.ini", "MYSQL", "DATABASE", database);
	port = CUtil::getINIint("configure.ini", "MYSQL", "PORT");
	numConn = CUtil::getINIint("configure.ini", "MYSQL", "NUMCONNS");
	if (numConn<1)
		numConn=1;

	//CConsole::outDebOnly("Generating %d MYSQL connections",numConn);

	MYSQL * mysql;

	CDb::numMysqlConnSemaphore=new CSemaphore(0, false);
	CDb::mysqlConnListMutex=new CSemaphore(0, false);

	//allow CDb::numMysqlConn Threads to get a Connection each
	CDb::numMysqlConn=numConn;
	CDb::mysqlConnListCountSemaphore = new CSemaphore(CDb::numMysqlConn, false);

	for(int conNum=0;conNum<CDb::numMysqlConn;conNum++)
	{
		mysql = mysql_init((MYSQL *) 0);
		if(mysql_real_connect(mysql, server, user, password, database, port, NULL, 0)) 
		{
			//Add connection to connection list
			CDb::mysqlConnList.push_back(mysql);
		}
		else
		{
			CConsole::outErr("Creation of new MYSQL connection failed");

			//decrease number of connections
			CDb::numMysqlConn-=1;

			//decrease semaphore
			CDb::mysqlConnListCountSemaphore->forceLock();		
		}
	}

	bool retVal=true;

	if (CDb::mysqlConnList.size()==0)
		retVal=false;

	//allow other threads to access the connList
	CDb::mysqlConnListMutex->forceUnlock();

	//allow other threads to access the numysqlConn value
	CDb::numMysqlConnSemaphore->forceUnlock();

	return retVal;
}

/*
int CDb::loadCharacters()
{
	MYSQL_RES * res;
	MYSQL_ROW row;
	unsigned char hash[16];
	char query[8192];
	int len;
	unsigned long * lengths;
	CChar * schar;

	strcpy(query, "SELECT a.charname, a.charid, a.acctid, a.ip, a.servip, a.sessionkey, b.targid, b.zone, b.x, b.y, b.z, c.look, c.jobs, c.skills, c.stat, c.health, b.prevzone, b.rot, c.equip, c.inv, c.keys FROM characters AS a, charpos AS b, charstats AS c WHERE a.charid = b.charid AND b.charid = c.charid");
	len = (int) strlen(query);

	MYSQL* mysqlConn=CDb::getConn();
	mysql_real_query(mysqlConn, query, len);

	res = mysql_store_result(mysqlConn);
	CDb::pushConn(mysqlConn);

	if(!(res))
	{
		return 0;
	}
 
	while((row = mysql_fetch_row(res)))
	{
		lengths = mysql_fetch_lengths(res);

		schar = new CChar();
		schar->lockAttributes();

		memcpy(schar->m_name, row[0], lengths[0]);
		memcpy((char *)&(schar->m_id), row[1], lengths[1]);
		memcpy((char *)&(schar->clientConn.acctID), row[2], lengths[2]);
		memcpy((char *)&(schar->clientConn.clientIP), row[3], lengths[3]);
		memcpy((char *)&(schar->clientConn.servIP), row[4], lengths[4]);

		memcpy((char *)(schar->enc.key), row[5], lengths[5]);

		md5((unsigned char *)schar->enc.key, hash, 20);
			
		int i;
		for(i = 0; i < 16; i++)
			if(hash[i] == 0)
				break;
		memset(hash+i, 0, 16-i);
		memcpy(schar->enc.hash, hash, 16);

		CBlowfish::init((char *)schar->enc.hash, 16, schar->enc.P, schar->enc.S[0]);

		memcpy((char *)&(schar->m_targId), row[6], lengths[6]);
		memcpy((char *)&(schar->loc.zone), row[7], lengths[7]);
		memcpy((char *)&(schar->loc.p.x), row[8], lengths[8]);
		memcpy((char *)&(schar->loc.p.y), row[9], lengths[9]);
		memcpy((char *)&(schar->loc.p.z), row[10], lengths[10]);
		memcpy((char *)&(schar->l), row[11], lengths[11]);
		memcpy((char *)&(schar->j), row[12], lengths[12]);
		memcpy((char *)&(schar->s), row[13], lengths[13]);
		memcpy((char *)&(schar->st), row[14], lengths[14]);
		memcpy((char *)&(schar->h), row[15], lengths[15]);
		memcpy((char *)&(schar->loc.prevzone), row[16], lengths[16]);
		memcpy((char *)&(schar->loc.p.rotation), row[17], lengths[17]);
		memcpy((char *)&(schar->e), row[18], lengths[18]);
		memcpy((char *)&(schar->inv), row[19], lengths[19]);
		memcpy((char *)&(schar->k), row[20], lengths[20]);

		schar->unlockAttributes();
	}
	mysql_free_result(res);
	
	return 1;
}
*/

void CDb::closeConn() 
{
	//make sure no one can change the amount of connections
	numMysqlConnSemaphore->forceLock();

	//make sure no connections are in use
	for(int i=0;i<CDb::numMysqlConn;i++)
	{
		CDb::mysqlConnListCountSemaphore->forceLock();
	}

	//make sure we are the only one accessing the connection list
	CDb::mysqlConnListMutex->forceLock();

	for(int i=0;i<CDb::numMysqlConn;i++)
	{
		mysql_close(CDb::mysqlConnList.back());
		CDb::mysqlConnList.pop_back();
	}

	CDb::numMysqlConn=0;

	//no need to release semaphores
}